/**
 * Copyright 2010 Victor Egea Hernando
 *
 * RockScript is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, version 3 of the License.
 *
 * RockScript is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with RockScript.  If not, see <http://www.gnu.org/licenses/>.
 */

void vmachine::op_new( vmachine &vm, corelib::int32 type, __register &reg ){
    // create a new object from class
    vm_class *iclass = vm._class[type]; object *obj;
    // create object
    if( type == vm._string_cidx ){
            obj = new ostring( iclass );
    }else if( type == vm._array_cidx ){
            obj = new oarray( iclass );
    }else if( type == vm._ibuffer_cidx ){
            obj = new obuffer<corelib::int32>( iclass );
    }else if( type == vm._rbuffer_cidx ){
            obj = new obuffer<corelib::real32>( iclass );
    }else if( type == vm._bbuffer_cidx ){
            obj = new obuffer<corelib::uint8>( iclass );
    }else{
            obj = new object( iclass );
    }
    regobject( reg ) = obj;
    // save object reference
    vm._objects0.insert( obj );
}

void vmachine::op_delete( vmachine &vm, __register &reg ){
	FLAGS_OFF( regobject( reg )->_flags, FLG_BLOCKED_OBJECT );
}

void vmachine::memory_stamp( vmachine &vm, std::ostream &os ){
	os << "<< memory_stamp >>" << std::endl;
	os << "heap >>"      << std::endl;
	os << "   objects[0] >> count:" << vm._objects0.size() << std::endl;
	os << "   objects[1] >> count:" << vm._objects1.size() << std::endl;
	os << "   objects[2] >> count:" << vm._objects2.size() << std::endl;
	// print backtrace
	backtrace( vm, os );
}

bool vmachine::jit_method( vmachine &vm, method &method ){
	#if PICOJIT_ENABLED == 1
		if( ( vm._flags & ( jit_enabled | debug_enabled ) ) == jit_enabled ){
			if( !( method._flags & FLG_MTHD_CPP ) ){
				// save cycles in 1/100 ratio
				if( method._cycles > vm._max_cycles ){
					vm._max_cycles = method._cycles;
				}
				// check if the method should be compiled
				if( !method.block().ready() && method._cycles >= 1000 ){
					if( ( vm._max_cycles / method._cycles ) >= 0.271828 ){
						if( vm._flags & print_jit ){
							float ratio = vm._max_cycles / method._cycles;
							std::cout << "<< jit_compìling >>" << std::endl;
							std::cout << "compiling method >> meta_name:" << method._meta_name;
							std::cout << ";cycles:" << method._cycles << ";max_cycles:";
							std::cout << vm._max_cycles << ";ratio:" << ratio << std::endl;
						}
						// compile method
						paper::paper2lir paper2lir; pjit::pjit_buffer buffer;
						// [1] TRANSLATE
						paper2lir.translate( vm, method, buffer );
						// [2] OPTIMIZE 1
						// ...
						// [3] OPTIMIZE ...
						// ...
						// [4] COMPILE( ASM )
						method.block().set_back_asm( vm._flags & print_jit );
						buffer.compile( method.block() );
						std::cout << method.block().asm_str();
					}
				}
				// check if the method should be executed
				return ( method.block().ready() );
			}else{
				// you can't jit methods with CPP code
				return( false );
			}
		}else{
			// you can't jit methods in debug mode
			return( false );
		}
	#else
		return( false );
	#endif // PICOJIT_ENABLED == 1
}

bool vmachine::op_kccall( vmachine &vm, corelib::uint32 ci, corelib::uint32 mi, bool &jitted ){
    // get method pointer
    method &nm = *vm._class[ci]->get_method( mi );
    // compile method through JIT if necessary and execute it
	// allocate stack frame
	if( !vm._aroutine->alloc_frame( nm ) ){
		std::stringstream ss; ss << "overflow in routine call-stack";
		send_error( vm, ss.str() );
		return( false );
	}
	// execute the block
	if( ( jitted = jit_method( vm, nm ) ) ){
		if( vm._flags & print_jit ){
			std::cout << "running method >> meta_name:";
			std::cout << nm._meta_name << std::endl;
		}
		pjit::pjit_block::execute( nm.block() );
	}
	return( true );
}

void vmachine::op_return( vmachine &vm, __register &reg ){
	// free stack frame
	vm._aroutine->free_frame();
	// load returning register
	if( vm._aroutine->_state != TERMINATED ){
		vm._aroutine->_ep[ SP_ARGUMENTS_THIS ] = reg;
	}else{
		vm._aroutine->_stack[ 0 ] = reg;
	}
}

bool vmachine::op_cppcode( vmachine &vm ){
	// check if it's a cpp function
	if( vm._aroutine->_mp->flags() & FLG_MTHD_CPP ){
		vm._aroutine->_pc += vm._aroutine->_mp->_mptr(
			vm, *vm._aroutine->_mp, vm._aroutine->_pc );
		return( true );
	}else{
		std::stringstream ss; ss << "the function doesn't have a cpp handler";
		send_error( vm, ss.str() );
		return( false );
	}
}

void vmachine::backtrace( vmachine &vm, std::ostream &os ){
	vm._aroutine->backtrace( os );
}

void vmachine::disassemble( vmachine &vm, std::ostream &os ){
	// print tag in XML style
	os << "<< disassemble >>" << std::endl;
	for(corelib::uint32 cit=0;cit<vm._class.size();cit++){
		// get method vector
		corelib::uint32 msize = vm._class[cit]->msize();
		// print class name in XML style
		os << "class >> idx:" << cit << ";name:" << vm._class[cit]->name() << std::endl;
		for(corelib::uint32 mit=0;mit<msize;mit++){
			method &meth = *vm._class[cit]->get_method( mit );
			// get opcode vector
			std::vector<instruction> &ov = meth._opcode;
			// print method name in XML style
			os << "   method >> idx:" << mit << ";meta_name:" << meth.meta_name() << std::endl;
			for(corelib::uint32 oit=0;oit<ov.size();oit++){
				// print opcode
				os << "      opcode >> " << oit << ": " << instruction( ov[oit] ).str() << std::endl;
			}
		}
	}
}

void vmachine::send_error( vmachine &vm, const corelib::ustring &str ){
	vm._hnd->dynError( vm_error( vm._aroutine->_id,
			vm._aroutine->_mp->pclass()->name(), vm._aroutine->_mp->name(), str ) );
}

void vmachine::garbage_collector( vmachine &vm, corelib::uint32 cycles ){
	// add cycles
	vm._gc_count += cycles;
	// GC process
	if( ( vm._gc_count & 0x7fffff ) == 0x7fffff ){
		/* COLLECT ALL OBJECTS ----------------------------------------------- */
		if( vm._flags & print_gc ){
			std::cout << "<< GC >> all generations" << std::endl;
		}
		// get sets
		std::set<object*> &objects0 = vm._objects0;
		std::set<object*> &objects1 = vm._objects1;
		std::set<object*> &objects2 = vm._objects2;
		std::set<object*>  _grey, _grey_tmp, _black;

		// START OF: collect objects from root registers -> grey set --------

		// singles from classes
		std::vector<vm_class*>::iterator cit = vm._class.begin();
		for(;cit != vm._class.end(); cit++) {
			// discard no-object registers and blocked objects
			object *obj = regobject( (*cit)->single() );
			// grey the object
			if( objects0.erase( obj ) || objects1.erase( obj ) || objects2.erase( obj ) ){
				_grey.insert( obj );
			}
		}
		// get values from stack frames
		__register *bottom;
		std::list<routine*>::iterator rit = vm._routine.begin();
		for(;rit != vm._routine.end(); rit++) {
			if( vm._flags & print_gc ){
				std::cout << "   routine >> index:" << (*rit)->_id << std::endl;
			}

			method* mp            = (method*)(*rit)->_mp;
			__register *rsp       = (*rit)->_rsp;
			__register *sp        = rsp - SP_CONTEXT_REGS;

			// iterate through stack frames
			do{
				if( vm._flags & print_gc ){
					std::cout << "      method >> meta_name:" << mp->meta_name();
					std::cout << ";rsp:" << ( rsp - (*rit)->_stack );
					std::cout << ";sfsize:" << mp->_sfstack << std::endl;
				}

				// get stack bottom
				bottom = rsp - mp->_sfstack - SP_NO_LOCAL_EXP_REG;
				// grey objects from scoped stack frame
				for( __register *pntr = bottom; pntr != sp; pntr++ ){
					object *obj = regobject( *pntr );
					// grey the object
					if( objects0.erase( obj ) || objects1.erase( obj ) || objects2.erase( obj ) ){
						_grey.insert( obj );
					}
				}

				mp    = (method*) rsp[-1]._vp;
				// free stack frame
				rsp   = bottom;
				sp    = rsp - SP_CONTEXT_REGS;
			}while( ( rsp - (*rit)->_stack ) > 1 );
			// print memory stamp
			if( vm._flags & print_gc ){
				memory_stamp( vm, std::cout );
			}
		}
		// END OF: collect objects from root registers -> grey set -----------

		// collect objects from other objects
		while( !_grey.empty() ){
			std::set<object*>::iterator oit = _grey.begin();
			for(;oit != _grey.end(); oit++) {
				std::vector<__register> &att = (*oit)->_attribute;
				for(std::vector<__register>::iterator attit=att.begin();attit!=att.end();attit++){
					object *obj = regobject( *attit );
					// grey the object
					if( objects0.erase( obj ) || objects1.erase( obj ) || objects2.erase( obj ) ){
						_grey_tmp.insert( obj );
					}
				}
				// black the object
				_black.insert( *oit ); _grey.erase( oit );
			}
			// swap sets and iterate again if not empty
			_grey.swap( _grey_tmp );
		}
		// delete white objects
		std::set<object*>::iterator oit;
		for(oit = objects0.begin();oit != objects0.end(); oit++) delete *oit;
		for(oit = objects1.begin();oit != objects1.end(); oit++) delete *oit;
		for(oit = objects2.begin();oit != objects2.end(); oit++) delete *oit;
		// clear and refill the last generation
		objects0.clear(); objects1.clear(); objects2.clear();
		objects2.swap( _black ); vm._gc_count = 0;
	}else{
		corelib::int32 gen = -1;
		if( ( vm._gc_count & 0x7ffff ) == 0x7ffff ){ // generation 3
			gen = 2;
		}else if( ( vm._gc_count & 0x7fff ) == 0x7fff ){ // generation 2
			gen = 1;
		}else if( ( vm._gc_count & 0x7ff ) == 0x7ff ){ // generation 1
			gen = 0;
		}
		/* COLLECT UNLOCKED OBJECTS ------------------------------------------ */
		if( gen >= 0 ){
			std::set<object*> *objects;
			std::set<object*> *nobjects;
			std::set<object*>::iterator oit;
			// debug
			if( vm._flags & print_gc ){
				std::cout << "<< GC >> generation " << gen << std::endl;
			}
			// get generations
			switch( gen ){
				case 0:{
						objects  = &vm._objects0;
						nobjects = &vm._objects1;
						break;
				}case 1:{
						objects  = &vm._objects1;
						nobjects = &vm._objects2;
						break;
				}case 2:{
						objects = &vm._objects2;
						break;
				}
			}
			// delete unlocked objects
			for(oit = objects->begin();oit != objects->end(); oit++){
					if( !( (*oit)->_flags & FLG_BLOCKED_OBJECT ) ){
							delete *oit; objects->erase( oit );
					}
			}
			// print objects left
			if( vm._flags & print_gc ){
				std::cout << "   " << objects->size() << " objects left" << std::endl;
				memory_stamp( vm, std::cout );
			}
			// promote
			if( gen < 2 ) {
					nobjects->insert( objects->begin(), objects->end() ); objects->clear();
			}
		}
	}
}

